Skip to content

feat(claude-ops): add inventory skill enumerating the invocable surface - #2313

Merged
kyle-sexton merged 4 commits into
mainfrom
feat/claude-ecosystem-inventory
Aug 12, 2026
Merged

feat(claude-ops): add inventory skill enumerating the invocable surface#2313
kyle-sexton merged 4 commits into
mainfrom
feat/claude-ecosystem-inventory

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds /claude-ops:inventory — a read-only report answering "what can this machine actually invoke, and where did each thing come from": built-in CLI commands with aliases and hidden/gated markers, bundled skills, and every component of every installed plugin across all marketplaces.

Why the binary is read. Upstream publishes no built-in slash-command list. docs/en/slash-commands.md and docs/en/skills.md return byte-identical markdown — commands were merged into skills — so no documentation source is complete for the built-in surface. Plugin components are read from disk. The two sources stay separated in the report because they are evidence of different quality.

Why it survives releases. Everything that changes between builds is resolved at runtime: registrar names come from the bundle's export maps (registerBundledSkill:()=>xu) rather than a hardcoded minified identifier; the bundle is located by export-name anchor rather than section layout; command fields are read by brace depth rather than a text window, since adjacent minified literals otherwise bleed into one another.

Why it tells on itself. The failure that matters is not a crash — a renamed export throws and is obvious. It is a new registration path, which returns a clean, confident, short list with no error. Every run therefore carries an integrity verdict (ok/degraded/broken) fed by canary commands, a minimum resolved-to-token ratio, a sweep for unrecognised registrar-shaped exports, and the resolved-versus-seen gap on bundled skills. A degraded run reports counts as floors, not totals. --self-check prints one verdict line and exits 0/1/2 for use as a CI gate or scheduled drift check, with /claude-ops:changelog as the natural trigger. VALIDATED_AGAINST records the last human-verified build, so a consumer on an older plugin against a newer CLI is told its counts are believed rather than verified.

Filters accept either a flag (--builtin, --plugins, --marketplace <name>, --agents, --hooks, --diff) or the equivalent sentence; one extraction feeds every view.

Scope boundary: this inventories capabilities that resolve; /claude-ops:audit-install-state inventories files on disk; /claude-ops:plugins keeps the fleet current; /claude-config:audit owns settings drift. No hooks, no new dependencies, no mutation — the binary is opened read-only and never executed.

Test plan

  • check-skill.sh — PASS, 0 errors, 1 soft warning (209 lines vs 200 target)
  • check-evals-quality.sh — PASS, 0 warnings, 6 eval cases
  • check-skill-portability.sh — no unexcused coupling tokens
  • check-skill-leaf-names.shinventory unique across all 65 plugins
  • validate-plugins.sh + validate-plugin-contracts.mjs — 2317 files, catalog in sync
  • generate-cheatsheet.mjs --check — in sync
  • test_inventory.py — 25 hermetic tests, 0.002s, no binary required
  • Live run against 2.1.228: 25,044,598-byte bundle, 190,487 brace pairs, 104 built-in commands, 33 bundled skills, security-review correctly flagged plugin-backed, 4 marketplaces / 121 plugins, 2.7s
  • --self-check correctly reports DEGRADED (exit 2) on the live build: 36 bundled-skill registrations seen, 33 resolved, 3 using computed names — the list is reported as a floor rather than a total
  • All gates re-run green after rebase onto main (10 commits)

Upstream claims were verified from raw markdown endpoints (curl -sSL .../page.md), not summarizing fetches, and both carry a recheck trigger in the skill body.

Related

No linked issue — this skill originated from an ad-hoc session that reverse-engineered the built-in command surface by hand, and the work was captured as a repeatable skill rather than filed as an issue first.

  • Sibling scope, not superseded: /claude-ops:audit-install-state (files on disk), /claude-ops:plugins (fleet currency), /claude-config:audit (settings drift). This PR adds a fourth object — capabilities that resolve — and cross-links all three from its scope-boundary table.
  • Follow-on candidate, deliberately not in this PR: a generic binary-exploration skill. It would ship on speculation today; this one is backed by an end-to-end run.

🤖 Generated with Claude Code

@cursor

cursor Bot commented Aug 12, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

Answers "what can this machine actually invoke, and where did each thing
come from" in one read-only report: built-in CLI commands with aliases and
hidden/gated markers, bundled skills, and every component of every installed
plugin across all marketplaces.

The built-in and bundled surfaces are read from the shipped binary because
upstream publishes no built-in command list — docs/en/slash-commands and
docs/en/skills return byte-identical markdown since commands were merged into
skills — so no documentation source is complete for them. Plugin components
are read from disk. The two sources stay separated in the report because they
are evidence of different quality.

Extraction resolves at runtime what changes between releases: registrar names
come from the bundle's export maps rather than a hardcoded minified
identifier, the bundle is found by export-name anchor rather than section
layout, and command fields are read by brace depth rather than a text window,
since adjacent minified literals otherwise bleed into one another. Python
3.11+ only — no strings, jq, or PowerShell.

Every run carries an integrity verdict, because the failure that matters is
not a crash but a clean-looking short list. Canary commands, a minimum
resolved-to-token ratio, a sweep for unrecognised registrar-shaped exports,
and the resolved-versus-seen gap on bundled skills each turn a quiet
shortfall into a stated one; a degraded run reports counts as floors.
--self-check exits 0/1/2 for use as a CI gate or scheduled drift check.

Filters accept a flag or the equivalent sentence; one extraction feeds every
view. 25 hermetic tests cover the brace reader, constant resolution, and each
integrity verdict against synthetic fixtures, so the suite does not change
its answer when the installed CLI updates.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kyle-sexton
kyle-sexton force-pushed the feat/claude-ecosystem-inventory branch from 1292851 to eb626bf Compare August 12, 2026 03:15

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 12928518b1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/claude-ops/skills/inventory/scripts/inventory.py Outdated
Comment thread plugins/claude-ops/skills/inventory/scripts/inventory.py Outdated
Comment thread plugins/claude-ops/skills/inventory/scripts/inventory.py Outdated
Comment thread plugins/claude-ops/skills/inventory/scripts/inventory.py
Comment thread plugins/claude-ops/skills/inventory/scripts/inventory.py
@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 25s —— View job


Security Review — PR #2313

  • Gather context (PR diff, changed files)
  • Read changed files (inventory.py, test_inventory.py, SKILL.md, extraction.md, evals.json, plugin.json, CHANGELOG.md, CATALOG.md, SKILL-CHEAT-SHEET.md)
  • Analyze for security issues (injection, unsafe file/binary handling, path traversal, secrets)
  • Post findings

Result: No security issues found

I reviewed only the files changed in this PR (git diff origin/main...HEAD), focused on plugins/claude-ops/skills/inventory/scripts/inventory.py (893 lines, the actual attack surface — it's the only new executable code) plus its test file and the three markdown docs.

What the script does, from a trust-boundary perspective:

  • Reads the local claude binary and local JSON config files (settings.json, known_marketplaces.json, plugin manifests) — all already-local, already-trusted-or-not-newly-exposed data.
  • Parses everything with re/json.load — no eval, exec, pickle, subprocess, or shell=True anywhere in the diff.
  • Writes only to a caller-specified --out path (local CLI arg, not attacker-reachable).
  • Never executes or modifies the binary it reads (confirmed: only .read_bytes() / .stat() calls on it).

Specific things I checked and ruled out:

  • Path handling (config_dir(), --binary, --config-dir, scan_marketplace's use of installLocation from disk JSON): all local-CLI-controlled or read from config the user already owns; no elevation of privilege, no traversal into a new trust domain — worst case is reading/reporting on directories already reachable by the invoking user.
  • Regex extraction functions (_TYPE_RE, _NAME_RE, extract_plugin_backed's bounded {0,900} lookahead loop, etc.): bounded quantifiers, no catastrophic-backtracking shape, and the PR's own test plan reports a 2.7s run against a 25MB real bundle — consistent with no ReDoS risk.
  • _load_json: pure json.load, no unsafe deserialization; a malicious plugin.json's dependencies field is echoed verbatim into the JSON report, but that's inert data in a locally-written report, not a code-execution path.
  • SKILL.md / reference/extraction.md: instructs the agent to run the script and to curl -sSL two hardcoded upstream URLs (code.claude.com/docs/..., raw.githubusercontent.com/anthropics/claude-code/...) for doc-drift verification — no user-controlled or externally-sourced URL construction, no command injection surface.
  • No GitHub Actions files are touched by this PR — the workflow-trigger/permissions/supply-chain concerns in my review scope don't apply here.
  • test_inventory.py: hermetic unit tests against synthetic strings, no I/O beyond importing the module.

This is a read-only, local-only diagnostic tool with no new injection, authorization, or secret-handling surface. Nothing to flag.

@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 4m 30s —— View job


Claude's review

Todo list

  • Gather context (CLAUDE.md, REVIEW.md, diff)
  • Review inventory.py (extraction logic, error handling)
  • Review test_inventory.py (test coverage)
  • Review SKILL.md and reference docs
  • Review plugin manifest/changelog/catalog updates
  • Post findings as inline comments + summary

Summary

Reviewed the changed files for correctness, error handling, test coverage, and maintainability (this repo has a claude-security-review workflow, so security findings are out of scope for this lane per REVIEW.md). Two 🔴 Important correctness/error-handling findings, posted inline:

  1. scripts/inventory.py#L789-L797--self-check swallows its own diagnostic on the most common failure. When no claude executable is found at all, the "why" lands under the key "reason", but the --self-check reader in main() only ever looks up "error" (L870-872) — so a fresh machine with no claude on PATH prints the generic BROKEN: binary source unavailable instead of the specific reason pick_binary() already computed. This undercuts the PR's own stated design goal for this feature ("every run ... tells on itself").

  2. scripts/inventory.py#L517-L544 — bundled-skill fields can bleed from the next registration. extract_bundled_skills reads aliases/gated/hidden/description from a fixed forward text window rather than bounding to the current xu({...}) call's own close, so a skill missing one of those optional fields inherits it from whichever skill is registered next. Traced through the test fixture: dataviz (no aliases of its own) would resolve aliases: ["checkup"]doctor's aliases — because the search keeps scanning past dataviz's own object. This is exactly the failure mode build_brace_map was built to prevent for command objects (documented in reference/extraction.md as "the single most important correctness property in the script"), but that discipline wasn't extended to this path. Not caught by the existing suite, since test_constant_names_resolve only asserts on code-review's aliases, which happens to be populated on its own object.

Both come with a [Fix this →] link in the inline comment.

What I couldn't verify: Bash execution wasn't available in this review environment, so I could not run test_inventory.py myself; the PR's Test Plan claims (25 passing tests, live run against 2.1.228, etc.) are author-reported and unverified by me. Finding #2 is a logical trace through the code and the fixture in test_inventory.py, not an executed repro.

Otherwise: the extraction design (runtime registrar discovery, brace-depth field resolution for commands, integrity verdict with canaries/yield-ratio/registrar-sweep) is careful and well-documented; the manifest/changelog/catalog/cheat-sheet updates are mechanical and consistent with the new skill.

Comment thread plugins/claude-ops/skills/inventory/scripts/inventory.py
Comment thread plugins/claude-ops/skills/inventory/scripts/inventory.py
@github-actions

Copy link
Copy Markdown

Claude has reviewed this PR 1 time. The lane skips further automatic reviews after 5; deleting this comment resets the count.

Three spellchecker hits and two missing exec bits, all in the new inventory
skill:

- Dropped the illustrative `getBundledSkills:()=>dFo` half of the export-map
  snippet in reference/extraction.md. The identifier is a verbatim minified
  name and reads as a typo; only the registerBundledSkill mapping is
  load-bearing for the point the snippet makes.
- Renamed the synthetic fixture identifier lFo to dvz in test_inventory.py
  and spelled misattribute without the hyphen.
- Marked inventory.py and test_inventory.py executable, matching the exec bit
  the sibling audit-install-state scripts already carry.

No behaviour change; the 25 tests still pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 50s —— View job


Security Review — PR #2313

  • Gather context (PR diff, changed files)
  • Read changed files (inventory.py, test_inventory.py, SKILL.md, extraction.md, evals.json, plugin.json, CHANGELOG.md, CATALOG.md, SKILL-CHEAT-SHEET.md)
  • Analyze for security issues (injection, unsafe file/binary handling, path traversal, secrets, GitHub Actions risk)
  • Post findings

Result: No security issues found

Reviewed all 9 files changed in this PR (current HEAD 3fa2e33), with focus on scripts/inventory.py (893 lines — the only new executable code and the actual attack surface). No .github/workflows files are touched by this PR, so the GitHub Actions concerns in my review scope (trigger patterns, permissions, pull_request_target/workflow_run, script injection via the github context) don't apply here.

Trust-boundary analysis of inventory.py:

  • No subprocess, os.system, eval, exec, Popen, or shell=True anywhere in the file — confirmed by direct grep, not just inspection.
  • Every filesystem read is local, already-user-owned data: the shipped claude binary (.read_bytes()/.stat() only, never written or executed — confirmed at read_bundle), settings.json, known_marketplaces.json, and plugin manifests under the config dir, all parsed with json.load (_load_json, L660-L665) — no pickle, no unsafe deserialization.
  • --out, --binary, and --config-dir are local CLI arguments controlled by the invoking user, at the same trust level as the process itself — writing to --out or reading from --config-dir/--binary is not a privilege boundary crossing, and there's no scenario in the skill's usage (SKILL.md) where these are populated from untrusted/remote input.
  • installLocation from known_marketplaces.json (scan_disk) is data the claude CLI itself already wrote to local disk when the marketplace was added — used only as a Path for a read-only iterdir() scan, never interpolated into a command or path-joined with ..-style traversal risk beyond what the user already controls.
  • Regex extraction (_TYPE_RE, extract_plugin_backed's bounded {0,900} lookahead, etc.) uses bounded quantifiers with no catastrophic-backtracking shape; the PR's reported 2.7s run against a 25MB real bundle is consistent with no ReDoS.
  • manifest.get("dependencies") is echoed verbatim into the JSON report — inert data reflected into a locally-written file, not a code-execution or injection path.
  • SKILL.md/reference/extraction.md reference curl -sSL only against two hardcoded upstream URLs (code.claude.com/docs/..., raw.githubusercontent.com/anthropics/claude-code/...) for doc-drift verification — no user- or externally-controlled URL construction.
  • test_inventory.py is hermetic unit tests against synthetic in-memory strings — no I/O, no secrets.
  • plugin.json, CHANGELOG.md, CATALOG.md, SKILL-CHEAT-SHEET.md changes are description/version/doc-index updates only — no permission, hook, or scope changes.

This is a read-only, local-only diagnostic tool with no new injection, authorization, or secret-handling surface. (Note: the correctness/logic findings already raised by Codex and the earlier Claude review pass in this thread — field-window bleed in extract_bundled_skills, --self-check's error-key mismatch, marketplace-vs-installed scanning — are legitimate but out of scope for this security-only lane.)

melodic-standards-sync Bot and others added 2 commits August 12, 2026 03:35
Automated exact-file synchronization from
[`melodic-software/standards@117116ab79ef13c355bafc96c597682c225594d0`](melodic-software/standards@117116a)
(requested ref `main`).

No linked issue — recurring automated distribution, not tied to a
tracked change.

Managed component mappings applied to
`melodic-software/claude-code-plugins`:

- **actionlint**: `.github/actionlint.yaml` → `.github/actionlint.yaml`
(mode `100644`)
- **agent-orientation**: `components/agent-orientation/orientation.md` →
`AGENTS.md` (mode `100644`)
- **editorconfig-checker**: `.editorconfig-checker.json` →
`.editorconfig-checker.json` (mode `100644`)
- **gitleaks**: `.gitleaks.toml` → `.gitleaks.toml` (mode `100644`)
- **lychee**: `lychee.toml` → `lychee.toml` (mode `100644`)
- **markdownlint**: `.markdownlint-cli2.jsonc` →
`.markdownlint-cli2.jsonc` (mode `100644`)
- **node-runtime**: `.node-version` → `.node-version` (mode `100644`)
- **path-detection-guardrails**:
`components/path-detection/machine-path-patterns.sh` →
`plugins/guardrails/lib/path-detection/machine-path-patterns.sh` (mode
`100644`)
- **pr-convention-policy**:
`components/pr-convention-policy/package-lock.json` →
`.github/standards/pr-convention-policy/package-lock.json` (mode
`100644`)
- **pr-convention-policy**:
`components/pr-convention-policy/package.json` →
`.github/standards/pr-convention-policy/package.json` (mode `100644`)
- **pr-convention-policy**:
`components/pr-convention-policy/policy.json` →
`.github/standards/pr-convention-policy/policy.json` (mode `100644`)
- **pr-convention-policy**:
`components/pr-convention-policy/policy.schema.json` →
`.github/standards/pr-convention-policy/policy.schema.json` (mode
`100644`)
- **pr-convention-policy**:
`components/pr-convention-policy/pr-convention-policy.mjs` →
`.github/standards/pr-convention-policy/pr-convention-policy.mjs` (mode
`100755`)
- **pyright**: `components/pyright/pyrightconfig.json` →
`.github/standards/pyright/pyrightconfig.json` (mode `100644`)
- **repository-text**: `.editorconfig` → `.editorconfig` (mode `100644`)
- **repository-text**: `.gitattributes` → `.gitattributes` (mode
`100644`)
- **review-instructions**: `REVIEW.md` → `REVIEW.md` (mode `100644`)
- **runner-policy**: `components/runner-policy/package-lock.json` →
`.github/standards/runner-policy/package-lock.json` (mode `100644`)
- **runner-policy**: `components/runner-policy/package.json` →
`.github/standards/runner-policy/package.json` (mode `100644`)
- **runner-policy**: `components/runner-policy/policy.json` →
`.github/standards/runner-policy/policy.json` (mode `100644`)
- **runner-policy**: `components/runner-policy/policy.schema.json` →
`.github/standards/runner-policy/policy.schema.json` (mode `100644`)
- **runner-policy**:
`components/runner-policy/repository-policy.schema.json` →
`.github/standards/runner-policy/repository-policy.schema.json` (mode
`100644`)
- **runner-policy**: `components/runner-policy/runner-policy.mjs` →
`.github/standards/runner-policy/runner-policy.mjs` (mode `100755`)
- **shellcheck**: `.shellcheckrc` → `.shellcheckrc` (mode `100644`)
- **typos**: `_typos.toml` → `_typos.toml` (mode `100644`)

Review and merge this PR normally. Do not hand-edit these managed files
downstream; change their standards source instead.

## Related

-
[`melodic-software/standards@117116ab79ef13c355bafc96c597682c225594d0`](melodic-software/standards@117116a)
— source commit this sync distributes.

Co-authored-by: melodic-standards-sync[bot] <300666570+melodic-standards-sync[bot]@users.noreply.github.com>
- Build marketplace output from installed_plugins.json installPath records
- Merge enabledPlugins with local > project > user precedence
- Honor manifest-declared component paths when scanning plugins
- Include project .claude skills/agents/hooks/MCP in project_scope_components
- Degrade integrity when CLI version cannot be detected from a real bundle
- Set executable bits on inventory Python scripts

Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown

Warning

Automated security review did not complete — this is an infrastructure failure, not a review verdict.

Treat any Claude comment on this PR (including a placeholder like "I'll analyze this and get back to you") as incomplete, not "no findings."

The check is green on purpose, and it is not evidence. It certifies that a security pass ran, and this one did not complete — but the cause is outside this PR's control, so merging is deliberately left unblocked rather than locking every merge for the length of the outage. Nothing was reviewed at this head. Where this check is required, it is satisfied without that evidence; a human should review security-sensitive changes here before merging.

Re-run the job to retry the review; a new push also retries it only if the caller's pull_request triggers include synchronize (the canonical security caller keeps it). An automatic retry may already have run — it is skipped when a partial review could duplicate comments, or when the failure class needs an operator.

Re-running does NOT help for every class:

  • rate-limit that persists across re-runs, or auth — the credential or usage budget needs an operator; retrying will not clear it.
  • a run that exhausted its turn budget ("subtype":"error_max_turns" above) will exhaust it again. As the PR author, split the change into smaller PRs; raising --max-turns is a change to the caller workflow, not something you can set on this PR.

@github-actions

Copy link
Copy Markdown

Warning

Automated review did not complete — this is an infrastructure failure, not a review verdict.

Treat any Claude comment on this PR (including a placeholder like "I'll analyze this and get back to you") as incomplete, not "no findings."

Re-run the job to retry the review. A new push re-triggers this lane only if the caller's pull_request triggers include synchronize (the canonical caller omits it).
An automatic retry may already have run — it is skipped when a partial review could duplicate comments, or when the failure class needs an operator (auth).

@kyle-sexton
kyle-sexton merged commit b97ecdf into main Aug 12, 2026
35 checks passed
@kyle-sexton
kyle-sexton deleted the feat/claude-ecosystem-inventory branch August 12, 2026 04:16
kyle-sexton added a commit that referenced this pull request Aug 12, 2026
…age error

--self-check returned 2 for a degraded verdict, which is also argparse's
exit code for a usage error. A CI gate treating 2 as "degraded, warn"
would silently swallow a mistyped flag as a warning instead of failing.
Degraded is now 3, leaving 2 to argparse: 0 ok, 1 broken, 2 usage error,
3 degraded. Verified all four against the live build.

Also records the 0.30.1 changelog entry covering this and the review
findings carried over from #2313, which merged before those fixes landed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Aug 12, 2026
…cope coverage (#2350)

## Summary

Follow-up to #2313, which merged before these fixes landed — auto-merge
fired when its checks went green and squashed only the first two
commits, so main currently carries the defects below.

Six findings, all from the automated review on #2313 and all real:

**Correctness**

- **Bundled-skill fields could bleed from the next registration.** The
extractor read each registration through a fixed 4000-character window —
the exact failure mode `build_brace_map` exists to prevent for commands,
and the one `reference/extraction.md` names as the thing not to do. A
registration omitting a description adopted the following one's. Fields
are now bound to their own literal via the brace map; an unmatched brace
is counted and surfaced rather than silently skipped.
- **Manifest-declared component paths were ignored.**
`PLUGIN_COMPONENTS` carried a manifest key per component and a comment
claiming the manifest is read before the tree; nothing read it. A
declared path *replaces* the default directory, so scanning defaults
regardless reported components a plugin does not ship. Dotted keys
resolve the `experimental` block.
- **`--self-check` lost its diagnostic when no binary was found.**
`pick_binary` stores its explanation under `reason`; the self-check path
read only `error` and printed a generic message.
- **An unreadable CLI version passed silently.** It is itself a drift
signal, so it now degrades the verdict rather than skipping the
comparison.
- **Degraded and argparse's usage error both exited 2.** A CI gate
treating 2 as "degraded, warn" would silently swallow a mistyped flag.
Degraded is now **3**, leaving 2 to argparse: `0` ok, `1` broken, `2`
usage error, `3` degraded.

**Coverage**

- **Installed plugins and project scope were never read.** Only
marketplace catalogs were scanned, so a plugin installed from a
marketplace that is no longer cached was invisible.
`disk.installed_plugins` now walks the plugin cache, and catalog /
installed / enabled are reported as three distinct sets. A project's
`.claude` tree contributes skills, agents, and wired hook events that no
machine-scope scan sees; `--project-dir` defaults to cwd. Wired hook
*events* are reported, never hook scripts on disk — listing an
unreferenced script as a hook would repeat the present-versus-active
error the skill exists to avoid.

## Test plan

- [x] `test_inventory.py` — 31 hermetic tests (was 25), adding
field-binding non-bleed, manifest path resolution incl. dotted and array
forms, and the version-unknown advisory
- [x] One existing fixture corrected: it asserted `ok` against a build
the new version check correctly calls `degraded`
- [x] Exit codes verified individually against the live build: `0` ok,
`1` broken (missing binary, and a non-binary file), `2` usage error, `3`
degraded
- [x] Negative paths: nonexistent `--binary` (reason preserved in
output), mutually exclusive flags, empty config dir, non-binary file
- [x] Live run on 2.1.228: 104 built-in commands, 33 bundled skills,
`installed_plugins` 67 across 4 marketplaces vs 121 catalog entries —
the distinction this PR adds
- [x] `check-skill.sh` PASS 0 errors · `check-evals-quality.sh` PASS ·
portability clean · `validate-plugins.sh` clean · cheat sheet in sync

## Related

No linked issue. Direct follow-up to #2313 (merged), addressing that
PR's own review comments, which arrived after auto-merge had already
squashed and merged it.

- `claude-ops` 0.30.0 → 0.30.1.
- Still deliberately out of scope, unchanged from #2313: a generic
binary-exploration plugin.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants